/// File: Robotics4.NET\CMarshalGenerator\XMLBinManager\XMLBinManager.cs
/// 
/// ------------------------------------------------------------
/// Copyright (c) 2007
///   Federico Cicchi    (cicchi@di.unipi.it)
/// 
/// The use and distribution terms for this software are 
/// contained in the file named license.txt, which can be found 
/// in the root of this distribution.
/// By using this software in any fashion, you are agreeing to 
/// be bound by the terms of this license.
///
/// You must not remove this notice, or any other, from this
/// software.
/// ------------------------------------------------------------

#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
#endregion

namespace XmlBinManager
{
    #region Serializable Dictionary Class
    /// <summary>
    /// This class implements a generic Dictionary but is serializable too.
    /// </summary>
    /// <remarks>
    /// IXmlSerializable is documented as "for internal use only": 
    /// Microsoft is free to change this interface at any time, but I think using 
    /// this interface is the best way to serialize a generic Dictionary.
    /// </remarks>
    public class SerializableDictionary<TKey, TValue>
        : Dictionary<TKey, TValue>, IXmlSerializable
    {
        #region IXmlSerializable Methods
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
            XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
            bool wasEmpty = reader.IsEmptyElement;
            reader.Read();
            if (wasEmpty)
                return;
            reader.ReadStartElement("Dictionary");
            while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
            {
                reader.ReadStartElement("Item");
                reader.ReadStartElement("Key");
                TKey key = (TKey)keySerializer.Deserialize(reader);
                reader.ReadEndElement();
                reader.ReadStartElement("Value");
                TValue value = (TValue)valueSerializer.Deserialize(reader);
                reader.ReadEndElement();
                this.Add(key, value);
                reader.ReadEndElement();
                reader.MoveToContent();
            }
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            XmlSerializer keySerializer = new XmlSerializer(typeof(String));
            XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
            writer.WriteStartDocument();
            writer.WriteStartElement("Dictionary");
            foreach (TKey key in this.Keys)
            {
                writer.WriteStartElement("Item");
                writer.WriteStartElement("Key");
                keySerializer.Serialize(writer, key);
                writer.WriteEndElement();
                writer.WriteStartElement("Value");
                TValue value = this[key];
                valueSerializer.Serialize(writer, value);
                writer.WriteEndElement();
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
            writer.WriteEndDocument();
        }
        #endregion
    }
    #endregion

    #region Xml-Binary Map Generator Class
    /// <summary>
    /// The purpose of this class is to compress in binary a simple xml message 
    /// generated by a Robotics4.NET roblet.
    /// Compression affects only xml tags and attribute names. Attributes values are
    /// only int, double, float, bool and string and are not compressed.
    /// Each binary code is 8 bit length and the available range is is 0x80..0xFF.
    /// Value from 0x00 to 0x1F for now are not used. Value from 0x20 to 0x7F are 
    /// ANSI characters code. 
    /// Codes are assigned dynamically during the CMarshallGenerator execution.
    /// </summary>
    public class MapGenerator
    {
        #region Static Read-only Fields and Properties
        /// <summary>
        /// Max number of binary codes availables.
        /// </summary>
        private static readonly int maxCodes = 127;
        public int MaxCodes
        {
            get { return maxCodes; }
        }
        #endregion

        #region Fields and Properties
        /// <summary>
        /// This dictionary maps each string (tag name or attribute name) with
        /// his corrispondent binary code and is uses to remember the mapping. 
        /// </summary>
        private SerializableDictionary<String, Byte> conversionMap;
        public SerializableDictionary<String, Byte> ConversionMap
        {
            get { return conversionMap; }
            set { conversionMap = value; }
        }

        /// <summary>
        /// First free binary code.
        /// </summary>
        private Byte currentCode;
        public Byte CurrentCode
        {
            get { return currentCode; }
            set { currentCode = value; }
        }

        private string openXmlTagCode;
        public string OpenXmlTagCode
        {
            get { return openXmlTagCode; }
        }
        #endregion

        #region Constructors
        public MapGenerator(string openXmlTag)
        {
            conversionMap = new SerializableDictionary<string, byte>();
            currentCode = 0x7F;

            if (openXmlTag == null)
                openXmlTagCode = GenerateNextBinaryCode("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
            else
                openXmlTagCode = GenerateNextBinaryCode(openXmlTag);
        }

        public MapGenerator() : this(null) { }
        #endregion

        #region Methods
        /// <summary>
        /// This method return the binary code associated to the string passed. If the
        /// string has yet a binary code associated, return it. Return a new binary
        /// code otherwise.
        /// </summary>
        /// <param name="name">The string to code.</param>
        /// <returns>The binary code associated.</returns>
        /// <exception cref="AurgumentException">If the string parameter is null.</exception>
        /// <exception cref="BinaryCodeOverflowException">If there aren't more binary free codes.</exception>
        public String GenerateNextBinaryCode(String name)
        {
            if (name == null)
                throw new ArgumentException("Field or Class name can't be null.");
            if (currentCode == 0x00)
                throw new BinaryCodeOverflowException("Too many field. Can't generate all binary codes");

            if (conversionMap.ContainsKey(name))
                return (String.Format("0x{0:x2}", conversionMap[name]));
            else
            {
                currentCode++;
                conversionMap[name] = currentCode;
                return (String.Format("0x{0:x2}", currentCode));
            }
        }
        #endregion
    }
    #endregion

    #region Xml-Binary Message Converter Class
    /// <summary>
    /// This class is a simple encoder (and decoder, off course) for an 
    /// xml message, generated by a Roblet controller, in a binary message. This class
    /// used the XmlBinMap.xml file generated by CMarshalGenerator (PicMicro version).
    /// </summary>
    public class MessageConverter
    {
        #region Fields and Properties
        private SerializableDictionary<Byte, String> binToXml;
        private SerializableDictionary<String, Byte> xmlToBin;
        #endregion

        #region Constructor
        /// <summary>
        /// Construct the converter with xml --> binary mapping infos included in the .xml
        /// file passed as parameter.
        /// </summary>
        /// <param name="inputUriXml">The .xml file generated by CMarshalGenerator 
        /// (PicMicro version).</param>
        /// <seealso cref="CMarshalGenerator"/>
        /// <exception cref="ArgumentException">If .xml file doesn't exist.</exception>
        public MessageConverter(String inputUriXml)
        {
            if (!System.IO.File.Exists(inputUriXml))
                throw new ArgumentException("File {0} not found. ", inputUriXml);

            SerializableDictionary<String, Byte> map = new SerializableDictionary<string, byte>();
            XmlReader reader = XmlReader.Create(inputUriXml);
            map.ReadXml(reader);
            reader.Close();
            CreateDictionary(map);
        }
        #endregion

        #region Methods
        /// <summary>
        /// Encodes in binary the xml message passed as parameter. 
        /// Precondition: The xml message must be a Roblet Message generated by
        /// CMarshalGenerator.
        /// </summary>
        /// <seealso cref="CMarshalGenerator"/>
        /// <param name="msg">The .xml message string.</param>
        /// <returns>The encoded message as array of Byte.</returns>
        public Byte[] Encode(String msg)
        {
            if (msg == null)
                throw new ArgumentException("Parameter msg can't be null");

            foreach (String s in xmlToBin.Keys)
            {
                msg = msg.Replace(s, ((Char)xmlToBin[s]).ToString());
            }

            Byte[] binMsg = new Byte[msg.Length];
            for (int i = 0; i < msg.Length; i++)
            {
                binMsg[i] = (Byte)msg[i];
            }

            return binMsg;
        }

        /// Decodes in xml the binary message passed as parameter. 
        /// </summary>
        /// <param name="binMsg">The binary message as array of Byte.</param>
        /// <returns>The xml message as String.</returns>
        public String Decode(Byte[] binMsg)
        {
            StringBuilder message = new StringBuilder();
            for (int i = 0; i < binMsg.Length; i++)
            {
                if (binToXml.ContainsKey(binMsg[i]))
                    message.Append(binToXml[binMsg[i]]);
                else
                    message.Append((Char)binMsg[i]);
            }
            return message.ToString();
        }

        private void CreateDictionary(SerializableDictionary<String, Byte> map)
        {
            this.binToXml = new SerializableDictionary<Byte, String>();
            this.xmlToBin = new SerializableDictionary<String, Byte>();
            foreach (String s in map.Keys)
            {
                xmlToBin[s] = map[s];
                binToXml[map[s]] = s;
            }
        }
        #endregion
    }
    #endregion

    #region Exception Classes
    public class BinaryCodeOverflowException : Exception
    {
        const string overflowMessage = "Max number of binary code reached.";

        public BinaryCodeOverflowException(String auxMessage)
        : base(String.Format("{0} - {1}", overflowMessage, auxMessage))
        {}
    }
    #endregion
}
